package cn.hyatt.core.aspectj;


import cn.hyatt.core.annotation.RequestLog;
import cn.hyatt.core.event.RequestLogEvent;
import com.alibaba.fastjson.JSONObject;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 请求日志
 *
 * @author hyatt
 */
@Slf4j
@Aspect
@Component
@AllArgsConstructor
public class RequestLogAspect {
    private ApplicationEventPublisher publisher;

    /**
     * 切入点
     */
    @Pointcut("@annotation(cn.hyatt.core.annotation.RequestLog)")
    public void doPointcut() {
    }

    /**
     * 之前
     */
    @Before("doPointcut()")
    public void doBefore() {
        log.info("之前");
    }

    /**
     * 之后
     */
    @After("doPointcut()")
    public void doAfter(JoinPoint joinPoint) {
        log.info("之后");
    }


    /**
     * 返回后
     * 方法成功返回结果之后执行
     */
    @AfterReturning(pointcut = "doPointcut()", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, Object result) {
        log.info("方法成功返回结果之后执行");
        if (joinPoint instanceof MethodInvocationProceedingJoinPoint point) {
            // 获取方法名
            String methodName = joinPoint.getSignature().getName();
            // 获取目标对象
            //Object target = joinPoint.getTarget();
            // 获取代理对象
            // Object proxy = joinPoint.getThis();
            // 获取方法参数
            Object[] args = joinPoint.getArgs();

            // 获取方法对象
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            Method method = methodSignature.getMethod();

            // 获取方法参数名
            ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
            String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);

            // 获取方法上的注解
            RequestLog annotationRequestLog = method.getAnnotation(RequestLog.class);
            String title = null;
            if (annotationRequestLog != null) {
                title = annotationRequestLog.title();
            }


            RequestMapping annotationRequestMapping = method.getAnnotation(RequestMapping.class);
            GetMapping annotationGetMapping = method.getAnnotation(GetMapping.class);
            PostMapping annotationPostMapping = method.getAnnotation(PostMapping.class);
            PutMapping annotationPutMapping = method.getAnnotation(PutMapping.class);
            DeleteMapping annotationDeleteMapping = method.getAnnotation(DeleteMapping.class);
            PatchMapping annotationPatchMapping = method.getAnnotation(PatchMapping.class);

            String requestMethod = null;
            String requestUri = null;
            if (annotationRequestMapping != null) {
                RequestMethod[] methods = annotationRequestMapping.method();
                List<String> methodList = Arrays.stream(methods).map(RequestMethod::name).toList();
                requestMethod = methodList.toString();
                requestUri = annotationRequestMapping.name();
            } else if (annotationGetMapping != null) {
                requestMethod = RequestMethod.GET.name();
                requestUri = annotationGetMapping.name();
            } else if (annotationPostMapping != null) {
                requestMethod = RequestMethod.POST.name();
                requestUri = annotationPostMapping.name();
            } else if (annotationPutMapping != null) {
                requestMethod = RequestMethod.PUT.name();
                requestUri = annotationPutMapping.name();
            } else if (annotationDeleteMapping != null) {
                requestMethod = RequestMethod.DELETE.name();
                requestUri = annotationDeleteMapping.name();
            } else if (annotationPatchMapping != null) {
                requestMethod = RequestMethod.PATCH.name();
                requestUri = annotationPatchMapping.name();
            }

            List<String> excludeParamNames = null;
            if (annotationRequestLog != null) {
                excludeParamNames = Arrays.stream(annotationRequestLog.excludeParamNames()).toList();
            }
            Map<String, String> map = new HashMap<>();
            for (int i = 0; i < args.length; i++) {
                String parameterName;
                if (parameterNames != null) {
                    parameterName = parameterNames[i];
                } else {
                    parameterName = "param" + (i + 1);
                }
                String jsonString = JSONObject.toJSONString(args[i]);
                if (excludeParamNames == null || !excludeParamNames.contains(parameterName)) {
                    map.put(parameterName, jsonString);
                }
            }

            String methodParamJson = JSONObject.toJSONString(map);

            String resultParamJson = null;
            if (annotationRequestLog != null && annotationRequestLog.isSaveResponseData()) {
                resultParamJson = JSONObject.toJSONString(result);
            }

            RequestLogEvent event = new RequestLogEvent();
            event.setTitle(title);
            event.setRequestMethod(requestMethod);
            event.setRequestUri(requestUri);
            event.setMethodName(methodName);
            event.setMethodParam(methodParamJson);
            event.setReturnParam(resultParamJson);
            publisher.publishEvent(event);
        }

    }

    /**
     * 抛出后
     * 方法抛出异常后执行
     */
    @AfterThrowing(pointcut = "doPointcut()", throwing = "exception")
    public void doAfterThrowing(JoinPoint joinPoint, Exception exception) {
        log.info("方法抛出异常后执行");
    }
}
